/**
 * 
 */
package org.json.simple;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

/**
 * A JSON object. Key value pairs are unordered. JSONObject supports java.util.Map interface.
 * 
 * @author Roger Xu<roger.xu.2007@gmail.com>
 */
public class JSONObject extends HashMap<String, Object> implements JSONAware {

    private static final long serialVersionUID = -9124080356574751017L;

    private static int curly_brackets = 0;

    public JSONObject() {
        super();
    }

    /**
     * Allows creation of a JSONObject from a Map. After that, both the generated JSONObject and the Map can be modified
     * independently.
     * 
     * @param map
     */
    public JSONObject(final Map<String, Object> map) {
        super(map);
    }

    /**
     * Encode a map into JSON text and write it to out. If this map is also a JSONAware or JSONStreamAware, JSONAware or
     * JSONStreamAware specific behaviors will be ignored at this top level.
     * 
     * @param map
     *            The map object.
     * @param out
     *            The Writer object.
     * @param isPretty
     *            true if output in a pretty style format
     * @throws IOException
     */
    public static void writeJSONString(final Map<String, Object> map, final Writer out, final boolean isPretty)
            throws IOException {
        if (isPretty) {
            writePrettyJSONString(map, out);
        } else {
            writeComapctJSONString(map, out);
        }
    }

    @Override
    public void writeJSONString(final Writer out, final boolean isPretty) throws IOException {
        writeJSONString(this, out, isPretty);
    }

    /**
     * Convert a map to JSON text. The result is a JSON object. If this map is also a JSONAware, JSONAware specific
     * behaviors will be omitted at this top level.
     * 
     * @see org.json.simple.JSONValue#toJSONString(Object)
     * 
     * @param map
     *            The map object.
     * @param isPretty
     *            true if output in a pretty style format
     * @return JSON text, or "null" if map is null.
     */
    public static String toJSONString(final Map<String, Object> map, final boolean isPretty) {
        final StringBuilder sb = new StringBuilder();
        try {
            final JSONWriter<StringBuilder> out = new JSONWriter<StringBuilder>(sb);
            writeJSONString(map, out, isPretty);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return sb.toString();
    }

    @Override
    public String toJSONString() {
        return toJSONString(this, false);
    }

    @Override
    public String toJSONString(final boolean isPretty) {
        return toJSONString(this, isPretty);
    }

    @Override
    public String toString(){
        return toJSONString(false);
    }

    /**
     * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). It's the same as
     * JSONValue.escape() only for compatibility here.
     * 
     * @see org.json.simple.JSONValue#escape(String)
     * 
     * @param str
     * @return
     */
    public static String escape(final String str) {
        return JSONValue.escape(str);
    }

    /**
     * Encode a map into compact JSON text and write it to out.
     * 
     * @param map
     *            The map object.
     * @param out
     *            The Writer object.
     * @throws IOException
     */
    private static void writeComapctJSONString(final Map<String, Object> map, final Writer out) throws IOException {
        if (map == null) {
            out.append("null");
            return;
        }

        boolean first = true;

        out.append('{');
        for (final String key : map.keySet()) {
            if (first) {
                first = false;
            } else {
                out.append(',');
            }

            out.append('\"');
            out.append(escape(key));
            out.append('\"');
            out.append(':');
            JSONValue.writeJSONString(map.get(key), out, false);
        }
        out.append('}');
    }

    /**
     * Encode a map into pretty JSON text and write it to out.
     * 
     * @param map
     *            The map object.
     * @param out
     *            The Writer object.
     * @throws IOException
     */
    private static void writePrettyJSONString(final Map<String, Object> map, final Writer out) throws IOException {
        if (map == null) {
            out.append("null");
            return;
        }

        boolean first = true;
        final String newLine = System.getProperty("line.separator");
        final String tab = "    ";

        if (curly_brackets != 0) {
            out.append(newLine);

            for (int i = 0; i < curly_brackets; i++) {
                out.append(tab);
            }
        }

        out.append('{');
        curly_brackets++;

        out.append(newLine);

        for (final String key : map.keySet()) {
            if (first) {
                first = false;
            } else {
                out.append(',');
                out.append(newLine);
            }

            for (int i = 0; i < curly_brackets; i++) {
                out.append(tab);
            }

            out.append('\"');
            out.append(escape(key));
            out.append('\"');
            out.append(": ");

            JSONValue.writeJSONString(map.get(key), out, true);
        }

        out.append(newLine);
        curly_brackets--;

        for (int i = 0; i < curly_brackets; i++) {
            out.append(tab);
        }

        out.append('}');
    }

}
